//
//  ARKViewController.m
//  ARKitDemo
//
//  Created by Zac White on 8/1/09.
//  Copyright 2009 Zac White. All rights reserved.
//

#import "ARViewController.h"

#import <QuartzCore/QuartzCore.h>

@interface ARViewController (Public)
- (void)updateLocations:(NSTimer *)timer;
- (void)GoToDetail:(id)sender;
@end

@implementation ARViewController

@synthesize locationManager, accelerometerManager;
@synthesize centerCoordinate;

@synthesize scaleViewsBasedOnDistance, rotateViewsBasedOnPerspective;
@synthesize maximumScaleDistance;
@synthesize minimumScaleFactor, maximumRotationAngle;

@synthesize updateFrequency;

@synthesize debugMode = ar_debugMode;

@synthesize coordinates = ar_coordinates;

@synthesize delegate, locationDelegate, accelerometerDelegate;

@synthesize cameraController;

@synthesize ARorientation;

@synthesize _updateTimer;

- (id)init {
	if (!(self = [super init])) return nil;
	
	ar_debugView = nil;
	ar_overlayView = nil;
	
	ar_debugMode = NO;
	
	ar_coordinates = [[NSMutableArray alloc] init];
	ar_coordinateViews = [[NSMutableArray alloc] init];
	ar_buttons = [[NSMutableArray alloc] init];
	ar_tagcontrollers = [[NSMutableArray alloc] init];
	
	_updateTimer = nil;
	self.updateFrequency = 1 / 20.0;
	
	self.scaleViewsBasedOnDistance = NO;
	self.maximumScaleDistance = 0.0;
	self.minimumScaleFactor = 1.0;
	
	self.rotateViewsBasedOnPerspective = NO;
	self.maximumRotationAngle = M_PI / 6.0;
	
	self.wantsFullScreenLayout = YES;
	self.ARorientation = YES;
	
	return self;
}

- (void)viewDidLoad {
    [super viewDidLoad];
	
	
}

- (id)initWithLocationManager:(CLLocationManager *)manager {
	
	if (!(self = [super init])) return nil;
	
	ar_debugView = nil;
	ar_overlayView = nil;
	
	ar_debugMode = NO;
	
	ar_coordinates = [[NSMutableArray alloc] init];
	ar_coordinateViews = [[NSMutableArray alloc] init];
	ar_buttons = [[NSMutableArray alloc] init];
	ar_tagcontrollers = [[NSMutableArray alloc] init];
	
	_updateTimer = nil;
	self.updateFrequency = 1 / 20.0;	
	
	// Se coge el antiguo delegado para mantener la cadena de actualizaciones
	self.locationDelegate = manager.delegate;
	self.locationManager = manager;
	self.locationManager.delegate = self;
	//self.locationDelegate = nil;
	
	self.scaleViewsBasedOnDistance = NO;
	self.maximumScaleDistance = 0.0;
	self.minimumScaleFactor = 1.0;
	
	self.rotateViewsBasedOnPerspective = NO;
	self.maximumRotationAngle = M_PI / 6.0;
	
	self.wantsFullScreenLayout = YES;
	self.ARorientation = YES;
	
	return self;
}

// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
	[ar_overlayView release];
	ar_overlayView = [[UIView alloc] initWithFrame:CGRectZero];
	
	[ar_debugView release];
	
	if (self.debugMode) {
		ar_debugView = [[UILabel alloc] initWithFrame:CGRectZero];
		ar_debugView.textAlignment = UITextAlignmentCenter;
		ar_debugView.text = @"Waiting...";
		
		[ar_overlayView addSubview:ar_debugView];
	}
	
	[ar_overlayView setFrame:self.cameraController.view.bounds];
	
	if (self.debugMode) {
		[ar_debugView sizeToFit];
		
		if (self.ARorientation == YES) {
			[ar_debugView setFrame:CGRectMake(0,
											  ar_overlayView.frame.size.height - ar_debugView.frame.size.height,
											  ar_overlayView.frame.size.width,
											  ar_debugView.frame.size.height)];
		} else {
			ar_debugView.transform = CGAffineTransformMakeRotation((M_PI/2.0));
			
			[ar_debugView setFrame:CGRectMake(0.0,
											  0.0,
											  21.0,
											  320.0)];
		}

		
	}
	
	if (!_updateTimer) {
		
		self._updateTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateFrequency
														 target:self
													   selector:@selector(updateLocations:)
													   userInfo:nil
														repeats:YES];
	}
	
	self.view = ar_overlayView;
	
}

- (void)setUpdateFrequency:(double)newUpdateFrequency {
	
	updateFrequency = newUpdateFrequency;
	
	if (!_updateTimer) return;
	
	[_updateTimer invalidate];
	
	self._updateTimer = [NSTimer scheduledTimerWithTimeInterval:self.updateFrequency
													 target:self
												   selector:@selector(updateLocations:)
												   userInfo:nil
													repeats:YES];
}

- (void)setDebugMode:(BOOL)flag {
	if (self.debugMode == flag) return;
	
	ar_debugMode = flag;
	
	//we don't need to update the view.
	if (![self isViewLoaded]) return;
	
	if (self.debugMode) [ar_overlayView addSubview:ar_debugView];
	else [ar_debugView removeFromSuperview];
}

- (BOOL)viewportContainsCoordinate:(ARCoordinate *)coordinate {
	BOOL result;
	
	if (self.ARorientation == YES) {
		double centerAzimuth = self.centerCoordinate.azimuth;
		double leftAzimuth = centerAzimuth - VIEWPORT_WIDTH_RADIANS / 2.0;
		
		if (leftAzimuth < 0.0) {
			leftAzimuth = 2 * M_PI + leftAzimuth;
		}
		
		double rightAzimuth = centerAzimuth + VIEWPORT_WIDTH_RADIANS / 2.0;
		
		if (rightAzimuth > 2 * M_PI) {
			rightAzimuth = rightAzimuth - 2 * M_PI;
		}
		
		result = (coordinate.azimuth > leftAzimuth && coordinate.azimuth < rightAzimuth);
		
		if(leftAzimuth > rightAzimuth) {
			result = (coordinate.azimuth < rightAzimuth || coordinate.azimuth > leftAzimuth);
		}
		
		double centerInclination = self.centerCoordinate.inclination;
		double bottomInclination = centerInclination - VIEWPORT_HEIGHT_RADIANS / 2.0;
		double topInclination = centerInclination + VIEWPORT_HEIGHT_RADIANS / 2.0;
		
		BOOL resultinclination = (coordinate.inclination > bottomInclination && coordinate.inclination < topInclination);
		
		//check the height.
		result = result && resultinclination;
		
	} else {
		double centerAzimuth = self.centerCoordinate.azimuth;
		double leftAzimuth = (centerAzimuth - VIEWPORT_HEIGHT_RADIANS / 2.0);
		
		if (leftAzimuth < 0.0) {
			leftAzimuth = 2 * M_PI + leftAzimuth;
		}
		
		double rightAzimuth = centerAzimuth + VIEWPORT_HEIGHT_RADIANS / 2.0;
		
		if (rightAzimuth > 2 * M_PI) {
			rightAzimuth = rightAzimuth - 2 * M_PI;
		}
		
		result = (coordinate.azimuth > leftAzimuth && coordinate.azimuth < rightAzimuth);
		
		if(leftAzimuth > rightAzimuth) {
			result = (coordinate.azimuth < rightAzimuth || coordinate.azimuth > leftAzimuth);
		}
		
		
		double centerInclination = self.centerCoordinate.inclination;
		double bottomInclination = centerInclination - VIEWPORT_WIDTH_RADIANS  ;
		double topInclination = centerInclination + VIEWPORT_WIDTH_RADIANS  ;
		
		BOOL resultinclination = (coordinate.inclination > bottomInclination && coordinate.inclination < topInclination);
		
		//NSLog(@"coordinate: %@ result: %@ inclination: %@", coordinate, result?@"YES":@"NO", resultinclination?@"YES":@"NO");
		
		//check the height.
		result = result && resultinclination;
	}

	//NSLog(@"coordinate: %@ result: %@ ", coordinate, result?@"YES":@"NO");
	
	return result;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	return (interfaceOrientation == UIInterfaceOrientationPortrait);
}

- (void)startListening {
	
	//start our heading readings and our accelerometer readings.
	
	if (!self.locationManager) {
		locationManager = [[CLLocationManager alloc] init];
		
	} 	
	
	//we want every move.
	self.locationManager.headingFilter = kCLHeadingFilterNone;
	self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
	
	[self.locationManager startUpdatingHeading];
	[self.locationManager startUpdatingLocation];
	
	//steal back the delegate.
	self.locationManager.delegate = self;
	
	if (!self.accelerometerManager) {
		self.accelerometerManager = [UIAccelerometer sharedAccelerometer];
		self.accelerometerManager.updateInterval = 0.01;
		self.accelerometerManager.delegate = self;
	}
	
	if (!self.centerCoordinate) {
		self.centerCoordinate = [ARCoordinate coordinateWithRadialDistance:0 inclination:0 azimuth:0];
	}
}

- (CGPoint)pointInView:(UIView *)realityView forCoordinate:(ARCoordinate *)coordinate {
	
	CGPoint point;
	
	//x coordinate.
	
	if (self.ARorientation == YES) {
		double pointAzimuth = coordinate.azimuth;
		
		//our x numbers are left based.
		double leftAzimuth = self.centerCoordinate.azimuth - VIEWPORT_WIDTH_RADIANS / 2.0;
		
		if (leftAzimuth < 0.0) {
			leftAzimuth = 2 * M_PI + leftAzimuth;
		}
		
		if (pointAzimuth < leftAzimuth) {
			//it's past the 0 point.
			point.x = ((2 * M_PI - leftAzimuth + pointAzimuth) / VIEWPORT_WIDTH_RADIANS) * realityView.frame.size.width;
		} else {
			point.x = ((pointAzimuth - leftAzimuth) / VIEWPORT_WIDTH_RADIANS) * realityView.frame.size.width;
		}
		
		//y coordinate.
		
		double pointInclination = coordinate.inclination;
		
		double topInclination = self.centerCoordinate.inclination - VIEWPORT_HEIGHT_RADIANS / 2.0;
		
		point.y = realityView.frame.size.height - ((pointInclination - topInclination) / VIEWPORT_HEIGHT_RADIANS) * realityView.frame.size.height;
	} else {
		double pointAzimuth = coordinate.azimuth;
		
		//our x numbers are left based.
		double leftAzimuth = self.centerCoordinate.azimuth - VIEWPORT_HEIGHT_RADIANS / 2.0;
		
		if (leftAzimuth < 0.0) {
			leftAzimuth = 2 * M_PI + leftAzimuth;
		}
		
		if (pointAzimuth < leftAzimuth) {
			//it's past the 0 point.
			point.y = ((2 * M_PI - leftAzimuth + pointAzimuth) / VIEWPORT_HEIGHT_RADIANS ) * realityView.frame.size.height;
		} else {
			point.y = ((pointAzimuth - leftAzimuth) / VIEWPORT_HEIGHT_RADIANS ) * realityView.frame.size.height;
		}
		
		//y coordinate.
		
		double pointInclination = coordinate.inclination;
		
		double topInclination = self.centerCoordinate.inclination - VIEWPORT_WIDTH_RADIANS  / 2.0;
		
		point.x = realityView.frame.size.width - ((pointInclination - topInclination) / VIEWPORT_WIDTH_RADIANS ) * realityView.frame.size.width;
	}

	
	
	return point;
}

#define kFilteringFactor 0.05
UIAccelerationValue rollingY, rollingZ, rollingX;

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
	// -1 face down.
	// 1 face up.
	
	//update the center coordinate.
	
	//NSLog(@"x: %f y: %f z: %f", acceleration.x, acceleration.y, acceleration.z);
	
	//this should be different based on orientation.
	if (self.ARorientation == YES) {
		rollingZ  = (acceleration.z * kFilteringFactor) + (rollingZ  * (1.0 - kFilteringFactor));
		rollingX = (acceleration.y * kFilteringFactor) + (rollingX * (1.0 - kFilteringFactor));
		
		if (rollingZ > 0.0) {
			self.centerCoordinate.inclination = atan(rollingX / rollingZ) + M_PI / 2.0;
		} else if (rollingZ < 0.0) {
			self.centerCoordinate.inclination = atan(rollingX / rollingZ) - M_PI / 2.0;// + M_PI;
		} else if (rollingX < 0) {
			self.centerCoordinate.inclination = M_PI/2.0;
		} else if (rollingX >= 0) {
			self.centerCoordinate.inclination = 3 * M_PI/2.0;
		}
		
		if (self.accelerometerDelegate && [self.accelerometerDelegate respondsToSelector:@selector(accelerometer:didAccelerate:)]) {
			//forward the acceleromter.
			[self.accelerometerDelegate accelerometer:accelerometer didAccelerate:acceleration];
		}
		
	} else {
		rollingZ  = (acceleration.z * kFilteringFactor) + (rollingZ  * (1.0 - kFilteringFactor));
		rollingY = (acceleration.x * kFilteringFactor) + (rollingY * (1.0 - kFilteringFactor));
		
		if (rollingZ > 0.0) {
			self.centerCoordinate.inclination = atan(rollingY / rollingZ) + M_PI / 2.0;
		} else if (rollingZ < 0.0) {
			self.centerCoordinate.inclination = atan(rollingY / rollingZ) - M_PI / 2.0;// + M_PI;
		} else if (rollingY < 0) {
			self.centerCoordinate.inclination = M_PI/2.0;
		} else if (rollingY >= 0) {
			self.centerCoordinate.inclination = 3 * M_PI/2.0;
		}
		
		if (self.accelerometerDelegate && [self.accelerometerDelegate respondsToSelector:@selector(accelerometer:didAccelerate:)]) {
			//forward the acceleromter.
			[self.accelerometerDelegate accelerometer:accelerometer didAccelerate:acceleration];
		}
	}

	
}

NSComparisonResult LocationSortClosestFirst(ARCoordinate *s1, ARCoordinate *s2, void *ignore) {
    if (s1.radialDistance < s2.radialDistance) {
		return NSOrderedAscending;
	} else if (s1.radialDistance > s2.radialDistance) {
		return NSOrderedDescending;
	} else {
		return NSOrderedSame;
	}
}

- (void)addTagController:(UIViewController*)tag {
	[ar_tagcontrollers addObject:tag];
}

- (void)removeTagController:(UIViewController*)tag {
	[ar_tagcontrollers removeObject:tag];
}

- (UIViewController*)getTagController:(NSInteger)index {
	return [ar_tagcontrollers objectAtIndex:index];
}

- (void)removeAllTagControllers {
	[ar_tagcontrollers removeAllObjects];
}

- (void)addCoordinateView:(ARCoordinate *)coordinate {
	
	UIView *newview = [self.delegate viewForCoordinate:coordinate];
	
	[ar_coordinateViews addObject:newview];
	
	UIButton *detailbut = [[UIButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 166.0, 181.0)];
	[detailbut addTarget:self action:@selector(GoToDetail:) forControlEvents:UIControlEventTouchUpInside];
	[detailbut setTag:coordinate.index];
	[detailbut setTitle:@"" forState:UIControlStateNormal];
	[detailbut setTitle:@"" forState:UIControlStateSelected];
	
	[ar_buttons addObject:detailbut];
	[detailbut release];
}

- (void)addCoordinate:(ARCoordinate *)coordinate {
	[self addCoordinate:coordinate animated:YES];
}

- (void)addCoordinate:(ARCoordinate *)coordinate animated:(BOOL)animated {
	//do some kind of animation?
	[ar_coordinates addObject:coordinate];
		
	// Calculo del radio maximo
	if (coordinate.radialDistance > self.maximumScaleDistance) {
		self.maximumScaleDistance = coordinate.radialDistance;
	}
	
	//message the delegate.
	UIView *newview = [self.delegate viewForCoordinate:coordinate];
	[ar_coordinateViews addObject:newview];
	
	UIButton *detailbut = [[UIButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 166.0, 181.0)];
	[detailbut addTarget:self action:@selector(GoToDetail:) forControlEvents:UIControlEventTouchUpInside];
	[detailbut setTag:coordinate.index];
	[detailbut setTitle:@"" forState:UIControlStateNormal];
	[detailbut setTitle:@"" forState:UIControlStateSelected];
	
	[ar_buttons addObject:detailbut];
	[detailbut release];
}

- (void)addCoordinates:(NSArray *)newCoordinates {
	
	//go through and add each coordinate.
	for (ARCoordinate *coordinate in newCoordinates) {
		[self addCoordinate:coordinate animated:NO];
	}
}

- (ARCoordinate*)GetCoordinates:(NSInteger)index {
	return [ar_coordinates objectAtIndex:index];
}

- (UIView *)GetCoordinateView:(NSInteger)index {
	return [ar_coordinateViews objectAtIndex:index];
}

- (UIButton*)GetCoordinateButton:(NSInteger)index {
	return (UIButton*)[ar_buttons objectAtIndex:index];
}

- (void)removeAllCoordinateViews {
	UIView *view_i;
	UIButton *butt;
	for (int i=0; i<[ar_coordinateViews count]; i++) {
		view_i = [ar_coordinateViews objectAtIndex:i];
		[view_i removeFromSuperview];
		butt = (UIButton*)[ar_buttons objectAtIndex:i];
		[butt removeFromSuperview];
	}
	[ar_coordinateViews removeAllObjects];
	[ar_buttons removeAllObjects];
}

- (void)removeAllCoordinates {
	[ar_coordinates removeAllObjects];
}

- (void)removeCoordinate:(ARCoordinate *)coordinate {
	[self removeCoordinate:coordinate animated:YES];
}

- (void)removeCoordinate:(ARCoordinate *)coordinate animated:(BOOL)animated {
	//do some kind of animation?
	[ar_coordinates removeObject:coordinate];
}

- (void)removeCoordinates:(NSArray *)coordinates {	
	for (ARCoordinate *coordinateToRemove in coordinates) {
		NSUInteger indexToRemove = [ar_coordinates indexOfObject:coordinateToRemove];
		
		//TODO: Error checking in here.
		
		[ar_coordinates removeObjectAtIndex:indexToRemove];
		[ar_coordinateViews removeObjectAtIndex:indexToRemove];
		[ar_buttons removeObjectAtIndex:indexToRemove];
	}
}

- (BOOL)IsCoordinateViewsEmpty {
	if (!ar_coordinateViews || ar_coordinateViews.count == 0) {
		return YES;
	} else {
		return NO;
	}

}

- (void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading {
	if (self.ARorientation == YES) {
		self.centerCoordinate.azimuth = fmod(newHeading.magneticHeading, 360.0) * (2 * (M_PI / 360.0));
	} else {
		self.centerCoordinate.azimuth = fmod(newHeading.magneticHeading + 90.0, 360.0) * (2 * (M_PI / 360.0));
	}
	
	if (self.locationDelegate && [self.locationDelegate respondsToSelector:@selector(locationManager:didUpdateHeading:)]) {
		//forward the call.
		[self.locationDelegate locationManager:manager didUpdateHeading:newHeading];
	}
}

- (BOOL)locationManagerShouldDisplayHeadingCalibration:(CLLocationManager *)manager {
	
	if (self.locationDelegate && [self.locationDelegate respondsToSelector:@selector(locationManagerShouldDisplayHeadingCalibration:)]) {
		//forward the call.
		return [self.locationDelegate locationManagerShouldDisplayHeadingCalibration:manager];
	}
	
	return YES;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
	
	if (self.locationDelegate && [self.locationDelegate respondsToSelector:@selector(locationManager:didUpdateToLocation:fromLocation:)]) {
		//forward the call.
		[self.locationDelegate locationManager:manager didUpdateToLocation:newLocation fromLocation:oldLocation];
	}
		
}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
	if (self.locationDelegate && [self.locationDelegate respondsToSelector:@selector(locationManager:didFailWithError:)]) {
		//forward the call.
		return [self.locationDelegate locationManager:manager didFailWithError:error];
	}
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
	
	// Release any cached data, images, etc that aren't in use.
}

- (void)dealloc {
	
	[ar_debugView release]; ar_debugView = nil;	
	[ar_coordinateViews release]; ar_coordinateViews = nil;
	[ar_coordinates release]; ar_coordinates = nil;
	[ar_buttons release]; ar_buttons = nil;
	[ar_tagcontrollers release]; ar_tagcontrollers = nil;	
	[accelerometerManager release]; accelerometerManager = nil;
	[locationManager release]; locationManager = nil;
	
	if (_updateTimer != nil) {
		
		[_updateTimer invalidate];
		_updateTimer = nil;
		
	}
	
	[centerCoordinate release]; centerCoordinate = nil;
	
    [super dealloc];
	
}

#pragma mark -
#pragma mark Metodos privados


// Metodo que se redefine en la clase hija para recargar las distancias
- (void)updateRAInfo:(CLLocation *)newLocation {
	
}

- (void)unloadView {
	
	[ar_overlayView removeFromSuperview];
	[ar_overlayView release];
	ar_overlayView = nil;
	
	accelerometerManager.delegate = nil;
	self.accelerometerManager = nil;
	
	[locationManager stopUpdatingHeading];
	locationManager.delegate = nil;
	self.locationManager = nil;
	
	locationDelegate = nil;
	accelerometerDelegate = nil;
	
	self.cameraController = nil;
	
}


@end
